﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace NetworkData.Spider
{
    /// <summary>
    /// The main class for the spider. This spider can be used with the 
    /// SpiderForm form that has been provided. The spider is completely 
    /// selfcontained. If you would like to use the spider with your own
    /// application just remove the references to m_spiderForm from this file.
    /// 
    /// The files needed for the spider are:
    /// 
    /// Attribute.cs - Used by the HTML parser
    /// AttributeList.cs - Used by the HTML parser
    /// DocumentWorker - Used to "thread" the spider
    /// Done.cs - Allows the spider to know when it is done
    /// Parse.cs - Used by the HTML parser
    /// ParseHTML.cs - The HTML parser
    /// Spider.cs - This file
    /// SpiderForm.cs - Demo of how to use the spider
    /// 
    /// This spider is copyright 2003 by Jeff Heaton. However, it is
    /// released under a Limited GNU Public License (LGPL). You may 
    /// use it freely in your own programs. For the latest version visit
    /// http://www.jeffheaton.com.
    ///
    /// </summary>
    public class Spider
    {

        public delegate void ReportTo(object sender, ReportToEventArgs e);
        public event ReportTo Report;

        /// <summary>
        /// The URL's that have already been processed.
        /// </summary>
        private Hashtable _mAlready;

        /// <summary>
        /// URL's that are waiting to be processed.
        /// </summary>
        private Queue _mWorkload;

        /// <summary>
        /// The status for each URL that was processed.
        /// </summary>
        enum Status { StatusFailed, StatusSuccess, StatusQueued };

        /// <summary>
        /// How many URL's has the spider processed.
        /// </summary>
        private int _urlCount;

        /// <summary>
        /// The constructor
        /// </summary>
        public Spider()
        {
            SpiderDone = new Done();
            Reset();
        }

        /// <summary>
        /// Call to reset from a previous run of the spider
        /// </summary>
        public void Reset()
        {
            _mAlready = new Hashtable();
            _mWorkload = new Queue();
            Quit = false;
        }

        /// <summary>
        /// Add the specified URL to the list of URI's to spider.
        /// This is usually only used by the spider, itself, as
        /// new URL's are found.
        /// </summary>
        /// <param name="uri">The URI to add</param>
        public void AddUri(Uri uri)
        {
            Monitor.Enter(this);
            if (!_mAlready.Contains(uri))
            {
                _mAlready.Add(uri, Status.StatusQueued);
                _mWorkload.Enqueue(uri);
            }
            Monitor.Pulse(this);
            Monitor.Exit(this);
        }

        /// <summary>
        /// The URI that is to be spidered
        /// </summary>
        public Uri BaseUri { get; set; }

        /// <summary>
        /// The local directory to save the spidered files to
        /// 输出路径
        /// </summary>
        public string OutputPath { get; set; }

        /// <summary>
        /// The object that the spider reports its
        /// results to.
        /// </summary>
        //public SpiderForm ReportTo
        //{
        //    get
        //    {
        //        return m_spiderForm;
        //    }

        //    set
        //    {
        //        m_spiderForm = value;
        //    }
        //}

        /// <summary>
        /// Set to true to request the spider to quit.
        /// </summary>
        public bool Quit { get; set; }

        /// <summary>
        /// Used to determine if the spider is done, 
        /// this object is usually only used internally
        /// by the spider.
        /// </summary>
        public Done SpiderDone { get; private set; }

        /// <summary>
        /// Called by the worker threads to obtain a URL to
        /// to process.
        /// </summary>
        /// <returns>The next URL to process.</returns>
        public Uri ObtainWork()
        {
            Monitor.Enter(this);
            while (_mWorkload.Count < 1)
            {
                Monitor.Wait(this);
            }


            var next = (Uri)_mWorkload.Dequeue();
            if (Report != null)
                Report(this, new ReportToEventArgs(next.ToString(), _urlCount++));
            //if (m_spiderForm != null)
            //{
            //    m_spiderForm.SetLastURL(next.ToString());
            //    m_spiderForm.SetProcessedCount("" + (m_urlCount++));
            //    long etime = (System.DateTime.Now.Ticks - m_startTime) / 10000000L;
            //    long urls = (etime == 0) ? 0 : m_urlCount / etime;
            //    m_spiderForm.SetElapsedTime(etime / 60 + " minutes (" + urls + " urls/sec)");
            //}

            Monitor.Exit(this);
            return next;
        }

        /// <summary>
        /// Start the spider.
        /// </summary>
        /// <param name="baseUri">The base URI to spider</param>
        /// <param name="threads">The number of threads to use</param>
        public void Start(Uri baseUri, int threads)
        {
            // init the spider
            Quit = false;

            BaseUri = baseUri;
            AddUri(BaseUri);
            SpiderDone.Reset();

            // startup the threads

            for (int i = 1; i < threads; i++)
            {
                var worker = new DocumentWorker(this) { Number = i };
                worker.Start();
            }

            // now wait to be done

            SpiderDone.WaitBegin();
            SpiderDone.WaitDone();
        }
    }

    public class ReportToEventArgs : EventArgs
    {
        public string Name { get; set; }

        public int Count { get; set; }

        public ReportToEventArgs(string name, int count)
        {
            Name = name;
            Count = count;
        }
    }
}
